BMP(BLE Micro Pro)を使ってmeishi2を無線化したメモ


概要

タイトルそのまんま。


まず一言言わせてほしい。無線化自作キーボード、体験的に最高。沼だなあ、、

が、BMP関連は名前が分かりづらい、ググラビリティが低い。googleくんはおっ画像か?ってなってしまう。

この名前になった理由は理解できるんだけど、ううん、、周辺の色々と相まって、めちゃくちゃとっつきづらかった。


でもBPMだったらもっと最悪だったと思うのでいろんな葛藤があったんだろうか。邪推するより聞いてみるか。



謝辞

まず謝辞から始めよ、みたいな感じなので、この場を借りて。


meishi2を開発/販売してくださっているbiacco42さん、素敵な製品をありがとうございます。

おかげさまで最高のお絵描き生活をしてます。ありがとう小さいキーボード、、


BMPを開発/販売してくださっているsekigonさん、素晴らしい製品をありがとうございます。

BMPのおかげでmeishi2の無線化に成功し、QoLが10段階ほど上がりました。荒廃した人生に光が差した感じです。


えーそんであの、Aくん、見てるー? できたよーわーい、色々ありがとう。

身近な先駆者である君がいなければ絶対にのたれ死んでいた。何度世を呪おうと思ったかわからない。



meishi2って?

これ

https://shop.yushakobo.jp/products/834


4キーの内容を自分で変更、調整できる自作キーボード

ハンダづけの数も大したことないし作る = USB-micro経由でPCに繋いで動かすまではすごく簡単、、


そして物理キーは便利だ、、特に、特定のショートカットだけを超高速に扱いたい場合。


ProMicroというブレッドボード = 基盤がセットになっている。


ProMicroはmeishi2においては次のような性能を持っていた。

1. USB-microでPCと接続できる

2. 4キーを入力機器として配置できる

3. 4キーの内容を自由に設定できる


特にChromeブラウザを使ってWebUSB経由でmeishi2に対してダイレクトにキーアサインをセットできる機能があって、

キーの割り当て変更、調整がめちゃくちゃ楽だった。


ProMicroって?

Arduino互換な基盤。いやマジでこう書いてあるインターネットしかみたことねえわ。


Arduino互換 is 何?っていう問いにmeishi2に関連する範囲で書くと、


要するに

USB-micro端子がついてて、ヒューマンインターフェースデバイス(キーボードとか)をこの基盤に接続した上でPCとUSBで接続できて、

PCからはキーボードとして認識されたりするよ、っていう性能を持っている基盤のこと。


めちゃくちゃかいつまむとこう。


これだけのことが知りたかったが「って何?」が無限に出てくる世界ぽくて心理的3歳児になることしかできなかった。


具体的には ProMicro <- ProMini <- Arduino <- … と「あれと一緒です」「互換です」で紹介されまくってて、、



どうもこの世界、みなさん性能や要件ではなく~互換という記法で書こうとする。

理由は単純で、そうでも書かないと正確な記述や説明が大変すぎるからと理解したが。


開発の歴史の階段に乗り損ねるとめちゃくちゃ経緯の階段を上から降りていかないと「で、何?」が分かりづらい。

知りたいのは what for why developed であって what is the different between ~ ではない、、、


厄介なのが、「わかった、で、何?は諦める、ところでこれ(要件)って実現できる?」「~互換なのでできる」「oh,, how?」「~について調べて」となるところ。

結局互換を辿っていく羽目になるんじゃねーか。


これは結局正当で、

方法が多種かつ多様だからこうなっているという理解をしている。


互換性という、知識の体系的な構造をかなり読み込まないとcan I do this? が確認できねえ。 なるほどね!


多種多様な理由は簡単で、~ってできますか、に対して関連/関与/応答/協調動作するレイヤーが単純にめっちゃ多い。

・ハードウェア種/パーツ種

・ソフトウェア/関連ソフトウェア/簡便化するために生まれたツール/時代による積層


それぞれに~互換、という言葉が山ほど使われているのを知った時、かなり絶望したが?

いやまあニーズがあれば人間は困難を越えるさ、それはそうだけどさ。


これ師匠が居なかったらほんとに詰んでた。


BMPって?

これ

Pro MicroサイズのUSB対応nRF52マイコンボード(のぎけす屋)

https://booth.pm/ja/items/1177319


開発者のsekigonさんが詳細な資料を公開してくれている。

https://sekigon-gonnoc.github.io/BLE-Micro-Pro/#/



今回meishi2を無線化する用途で、嬉しかったBMPの性能はこの辺。

1. 電池を繋ぐ or USB-Cからの給電でBluetooth LowEnergyでPCと接続できる

2. 電源が入ったらその時にBLE接続登録済みの機器と接続される設定が可能

3. USB-CでPCと繋ぐと、BMP用のWebConfigurator(sekigonさん作)からコマンドラインを叩き込め、ここでBLEの接続設定ができる

4. USB-CでPCと繋ぐと設定ファイルなどがUSBストレージの形で表示されるのでゴリゴリ編集しながらキーを試せる



互換がある、の互換って何

Pro Microと互換性を意識~~と紹介されていて、互換とは、、ってなったけど買ってみて、ああなるほど互換ってこういうことかとなった。

いや必要な要件だけを学びたかったが、まあ、ニーズが自分の腕 = 知識に対して釣り合ってないから苦労した。



わかったこと from meishi2無線化

使ってみるまで知らなかった業界の常識みたいなのが多すぎて、互換とは??というあたりで無限に宇宙猫になったが、今回知ったのはこの辺。


ブレッドボードごとの互換性がある、とは次の要素のことを言う

1. pin番号/位置の一致を含まない

2. RESETとBOOTとか、互換があるという割には違うが -> なんとなく察しろ、用語が違っても用途は一緒な部分がある

3. 互換があるという言葉を気にするな、ひたすらドキュメントを読め、察しろ、探せ、試せ


何を当たり前のことを、と思ったひと、たぶんあなたが正しい。

ただ、互換がある、とだけ言われた時、自分には、「それってどこまで一緒ってこと、、?」ってのがマジでわからんかった。


こうするにはどうすればいいの? 自分用FAQ

先に書いておくとdocumentを読めば書いてあることがほとんど。

経験値不足によって「こういうことはdocumentに書いてあるはず」というのが、まあ「読んだが、、こういう意味だったのか、、」になかなかならなかった。

書いてあることを実際に動作させまくることで、観測/仮説/検証しまくった。

醍醐味、、かなあ、面白かった。



Q1. BMPはなんて名前でBLE接続に表示されるの?

A.1 (BMP)ble_micro_pro です(undocumented)

このへんはトラブルシュートに直結した。

どんなスイッチを入れたらBMPがBLEのアドバタイズをするのか、てっきり一つも登録がない状態で電源が入ったら自動的にやるもんだと思い込んでいた。

iOSとかAndroidでBLEに触れてたから尚更「基盤になんもスイッチとかついてないけどどうやってアドバタイズ開始すんのこれ」ってなってた。


BMPが何したらBLE接続のアドバタイズをするのかが、事前情報として全くわからなかった。これは後述で解決される。

いやおうちの近辺無限にBLEのアドバタイズ出ててさあ、、なんもせんでもぺこぺこ増えたり減ったりするのよ、BLEのadvが。

山か。山に行けばよかったか?



Q2. BMPはどうやったらBLEのアドバタイズを開始するの?

A2. CLIからBLEのアドバタイズを実行するコマンドが用意されててそれ叩くと起動 or キー割り当てして押せ。それ以外の方法はdocで紹介されてない。

これはなんかまあきっと常識というか「考えたら自明だからこうです」なんだろうね~~~ってなった。


document曰く、CLIの情報が「発展編」っていう基礎編とは切り離されたコーナーにあったんで、あ~~まあきっと発展以外の手段もあるんだろう、と思いこんだのが行けなかった。


起動 = 通電したらそのままBLEの登録を発行するのかなって思い込んでたのも行けなかった。初期設定だとBLEのadvはしてませんとも書いてなかったんだが、まあ想定しないんだよなきっと。

思い込みがよくねー。

基礎編まわりザーッとみてもわかんねーなー~~ってなったのでCLIに書いてあるコマンドをとりあえず一通り実行してみようとなって、発見した。


CLI以外の手段として「BMPのコントロールをキーに割り当てられます」というのもあったが、そのコントロール一覧に含まれていたBLE adv開始みたいなのがあるのをパッとは見つけられなかった。


いや見つけててもえーー1キーそれに潰すのー?ってなってたと思うけど。実際そうなりかけたが。



Q3. 電源をつけた状態のBMPに対して、BLEの登録も済んでて、それなのにPC側からBLEで接続しにいくと蹴られるんだけど。

A3. PC側からは接続できません。configにstartupっていう項目があって、それの初期値が0なところを1に書き換えて電源を繋ぐとBMPが接続してきます。


このへんは実はdocumentの中でも「古い資料」っていうコーナーに情報があって、

古い資料

https://sekigon-gonnoc.github.io/BLE-Micro-Pro/#/deprecated/README?id=%e4%bd%bf%e3%81%84%e6%96%b9%e3%82%bd%e3%83%95%e3%83%88%e3%82%a6%e3%82%a7%e3%82%a2


この古い情報の中に、startup_~みたいな関数の記述があって、おや、、?ってなって、そういや最新verのconfigファイルにもstartupの文字があったな、、となり、

0だったので1にしてから電源を入れると無事電源投入時にPCへと接続された。やったね!!


ちなみに電源を入れる = USB-CでPCと接続する or BMPに電池を繋ぐ、だよ。


あとはロータリーエンコーダ(クルクル回せるツマミスイッチ)のコーナーに、startupの項目を1にしてるconfigが載ってたりする。



太字のとこがstartupの設定。

{

"config": {

"version": 2,

"device_info": {

"vid": "0x0000",

"pid": "0x0000",

"name": "ble_micro_pro",

"manufacture": "sekigon-gonnoc",

"description": "A development board for wireless split keyboards"

},

"matrix": {

"rows": 1,

"cols": 19,

"device_rows": 1,

"device_cols": 19,

"debounce": 1,

"is_left_hand": 1,

"diode_direction": 0,

"row_pins": [33],

"col_pins": [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22],

"layout": [

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18

]

},

"mode": "SINGLE",

"startup": 1,

"peripheral": {

"max_interval": 60,

"min_interval": 30,

"slave_latency": 7

},

"central": {

"max_interval": 60,

"min_interval": 30,

"slave_latency": 7

},

"led": {

"pin": 255,

"num": 128

},

"keymap": {

"locale": "US",

"use_ascii": 0

},

"reserved": [0, 0, 0, 0, 0, 0, 0, 0]

}

}


うまく行ったので文句はないけど、これを発見しなかったら次のようになっていた。

・BLEのアドバタイズをCLI or キー入力から実行できる(documented)

・なので1キーを接続のために潰して、押したら接続 or いちいちUSB-Cで繋いでChromeとかからCLIでBLE adv起動、、にしてた


meishi2は4キーなので1キーこれに使うとしたら投げ捨てていたし、いちいちPCに接続してBLE起動もやばすぎる。


備忘録として機能するように、Q3については「BMPで起動時にBLEのアドバタイズを行うには」「BMP 起動 BLE」とかも一応文字として書いておく。



書いてて思ったけど、これ意図せずstartupの項目についての説明がconfigの項目一覧から抜けてる感じなのかな、、


config.jsonの説明

https://sekigon-gonnoc.github.io/BLE-Micro-Pro/#/edit_config_file?id=configjson%e3%81%ae%e8%aa%ac%e6%98%8e


やっぱ意図的とは思えないや、githubで公開されてるんだしPR出してみるか。

-> 出してみた(2022/04/02 早朝)

-> マージされた!! わーい(2022/04/02 夕方)

-> 内容適当すぎたので再度出し直し、、すいません、、

-> fix typoしたのもマージしていただいた、感謝。(2022/04/02 夕方)


というわけで今は公式ドキュメントの方にもconfig.jsonのパラメータ一覧にstartupの項目が生えた! ちょー嬉しい。



Q4. meishi2用のconfig.jsonの書き方がマジでわからんかった。

A4. なんとかなった。

4キーしかないキーボードなんだけどconfigのmatrixやlayoutをどういじっても反応しなかった。

試行経路を書いておく。


meishi2でのキーとProMicroの接続

pin <- キー


6 <- 最左

7 <- 左

A2 <- 右

A1 <~ 最右


なのでこれをBMPに対応させると、


BMP vs PM

9 <- 6

10 <- 7

19 <- A2

18 <- A1

というピン番号になるんだけど、


どうconfig.jsonを書いたらいいかわからん問題

とにかくいろいろ試したがわからなかった。rows 1でcols 4とか、逆とか。

で、正解は以下

{"config":{"version":2,

"device_info":{"vid":"0x0000","pid":"0x0000","name":"ble_micro_pro","manufacture":"sekigon-gonnoc","description":"A development board for wireless split keyboards"},

"matrix":{"rows":2,"cols":2,"device_rows":2,"device_cols":2,"debounce":1,"is_left_hand":1,"diode_direction":0,"row_pins":[9,10],"col_pins":[19,18],

"layout":[

  1,  2,  3,  4]},

"mode":"SINGLE","startup":1,

"peripheral":{"max_interval":60,"min_interval":30,"slave_latency":7},

"central":{"max_interval":60,"min_interval":30,"slave_latency":7},

"led":{"pin":255,"num":128},

"keymap":{"locale":"US","use_ascii":0},

"reserved":[0,0,0,0,0,0,0,0]}}

太字の部分が設定。いや~~4キーに対してこれはわからんて、、



どうやってこの正解に辿り着いたかというと、ドキュメントにある通り、QMK用の設定からconfig.jsonを出力するスクリプトが用意されていて、

qmk用の設定から変換する

https://sekigon-gonnoc.github.io/BLE-Micro-Pro/#/edit_config_file?id=qmk用の設定から変換する


で、QMK用の設定 is 何、って思ったんだけど、これはQMKのリポジトリのコンパイル前のキーボードごとの.cとか.hとかを

フォルダごとBMPのドキュメントにあるスクリプトに食わせればいいという意味だった。


meishi2はQMKのリポジトリにファームウェアのビルド内容が含まれていたのを見つけて対応した。

meishi2のファームのビルドとかは必要ない。(どこまで何がいるのかわからんかったが闇雲に試したら行けた)



対応した/試した is 下記。


1. QMKのリポジトリをcloneするとかしてkeyboardsフォルダをゲットする。

https://github.com/qmk/qmk_firmware


より具体的には https://github.com/qmk/qmk_firmware/tree/master/keyboards/biacco42/meishi2 のkeyboardsフォルダ含めたmeishi2フォルダの一式だけがあればいい。

biacco42というmeishi2の作者さんのフォルダはあってもなくても行けた。


こんな感じ。

スクリーンショット 2022-04-02 16.27.19.png

2. pythonスクリプトをkeyboards/meishi2フォルダに対して実行

で、↑だとconv.pyの中身がドキュメントのスクリプトにしてあって、次のコマンドでmeishi2_config.jsonが出力される。(画像だと生成済み)

python3 conv.py ./keyboards/meishi2



生成されたmeishi2_config.jsonは以下の内容になっているはず。


{"config":

{

"version":2,

"device_info":{"vid":"0xBC42","pid":"0x0003",

"name":"meishi2","manufacture":"Biacco42","description":""},

"matrix":{"rows":2,"cols":2,"device_rows":2, "device_cols":2,

"debounce":1,"is_left_hand":1,"diode_direction":0,

"row_pins":[9, 10],

"col_pins":[19, 18],

"layout":[1, 2, 3, 4]},

"mode":"SINGLE","startup":1,

"peripheral":{"max_interval":60,"min_interval":30,"slave_latency":7},

"central":{"max_interval":60,"min_interval":30,"slave_latency":0},

"led":{"pin":255, "num":0},

"keymap":{"locale":"US","use_ascii":0}

}}

正直2x2になってるのとかマジでなんもわからんかったが、これをCONFIG.JSNというファイル名にして放り込むとmeishi2のキーボードが効いた!!


という感じで優勝してendです。対戦ありした。

上述されてるstartupの項目も1になったものが吐かれていてうん、、いいね、、うん、、



以下はオマケのQ&A

Q. BMPにUSB-C経由でのみバッテリーや電源を接続してBTキーボードとして使用することは可能か?

A. 可能。 動作した。

直接接続先がPCの場合でも、BT接続側があとであればそちらが優先されるようで、BTキーボードとして見えている側への入力が可能。


このため、旅先とかでボタン電池が切れちゃったーとなっても、USB-Cにモバイルバッテリーを接続して別端末のBTキーボードとして使うことは可能。


基盤に接続された電源とUSB-Cからの給電のどちらを優先するかはドキュメントに書いてある。


Q. USBストレージとして表示されるコンフィグファイルをmacで開くと空文字がガンガン入ったりバイナリとして表示されたりするんだけど

A. 実際にファイルが壊れている場合と、USBストレージとして接続した時にバグってる場合の2系統があるっぽく、だいたいストレージを取り出すと即自動接続され直して、そこから開くとファイルが直ってる。

かなり悩んだんだけど、うーん、ひたすらファイルが壊れやすい印象。なので自分は次のような操作だけをするように心がけた。

1. デスクトップに表示されるストレージをデスクトップに丸コピ

2. 丸コピしたフォルダの方をテキストエディタで編集

この時点でエディタで開いて文字がバグってるとかがあればストレージを取り出し -> 自動再接続で1からやり直し

3. 編集が終わったらストレージにファイルを放り込む



Q. ドキュメントからリンクされてるBMP用のQMK ConfiguratorでChrome経由でkeymap書き換えるとぶっちぎれない?

A. ぶっちぎれますね、、なんでだろう、、jsonファイルとしてDLして手で書いてたよ自分は。



Q. なんかconfigファイルとかがおかしくなったんですけど。

A. 困りましたね! 自分は面倒臭くなったらドキュメント -> ブートローダー書き直し -> Application書き直し ってやって何も無かったことにしたよ。



Q. BMPに繋げるそれっぽい電池ボックス大体売り切れだと思うけどどこでどうしたの。

A. どこかのストアでBMP化用の既存のキーボード拡張(キーボード自体は終売)というのを見かけて寸法や構成見た感じいけそうだったんで買った。

これ https://shop.dailycraft.jp/products/ext3_bmp_set




以下メモ


meishi2でのキーとProMicroの接続

pin <- キー


6 <- 最左

7 <- 左

A2 <- 右

A1 <~ 最右


なのでこれをBMPに対応させると、


BMP vs PM

9 <- 6

10 <- 7

19 <- A2

18 <- A1


みたいになるんだけど、BMPのコンフィグ上でこれらのセッティングを行ってもキーが効かないような、、


-> やっぱなんか勘違いしてそう。うーん。



次のスクリプトでQMK用のmeishi2のやつをコンフィグファイルに変換ってのをやってみるか、、頭動いてる時に。

#!/usr/bin/env python

# -*- coding:utf-8 -*-


import os

import sys

import re



def red(string):

    return '\033[31m' + string + '\033[0m'



def yellow(string):

    return '\033[33m' + string + '\033[0m'



def green(string):

    return '\033[32m' + string + '\033[0m'



class ConfigConverter:

    PIN_TABLE = {'D3': 1, 'D2': 2, 'D1': 5, 'D0': 6, 'D4': 7, 'C6': 8, 'D7': 9, 'E6': 10, 'B4': 11,

                 'B5': 12, 'B6': 13, 'B2': 14, 'B3': 15, 'B1': 16, 'F7': 17, 'F6': 18, 'F5': 19, 'F4': 20}


    def __init__(self):

        self.is_split = False

        self.vid = ''

        self.pid = ''

        self.name = ''

        self.manufacture = ''

        self.description = ''

        self.row_num = ''

        self.col_num = ''

        self.row_pins = ''

        self.col_pins = ''

        self.row_pins_right = ''

        self.col_pins_right = ''

        self.layout = ''

        self.led_pin = ''

        self.led_num = ''

        self.diode_direction = ''

        self.mode = ''

        self.layout_macro = []

        self.search_layout = ''


    def find_layout_macro(self, f):

        lines = f.readlines()

        layout_detect = False

        layout_str = ''

        line = 1

        for string in lines:

            string = self.remove_comment(string)

            layout_name = re.match(

                r'^\s*#\s*define\s+LAYOUT[^\r\n\t\f\v \\]*\s*', string)

            if layout_name is not None:

                layout_name = layout_name.group().split("#define")[1]

                layout_name = layout_name.replace('(', '')

                layout_name = layout_name.strip()

                file_name = f.name

                if 'keyboards/' in file_name:

                    file_name = file_name.split('keyboards/')[1]

                self.layout_macro.append(

                    {"name": layout_name, "file": file_name, "line": line})

            line = line + 1


    def set_layout_index(self, idx):

        idx = int(idx)

        if idx >= len(self.layout_macro):

            raise ValueError


        self.search_layout = self.layout_macro[idx]["name"]


    def print_layout_list(self):

        index = 0

        for layout in self.layout_macro:

            print(

                f'{index}: {layout["name"]}\t\t{layout["file"]}:{layout["line"]}')

            index = index + 1


    def convert_pindef(self, arg):

        str2 = arg.split('{')[1]

        str2 = re.sub(r'(\(|{|})', '', str2)

        str2 = str2.replace(' ', '')

        str2 = str2.replace('\n', '')

        str2 = str2.replace('\r', '')


        pin_array = []

        for pin in str2.split(','):

            if pin == '':

                continue

            if 'PIN' in pin:

                pin_array.append(pin.replace('PIN', ''))

            else:

                pin_array.append(self.PIN_TABLE[pin])

        pin_str = str(pin_array)


        return pin_str


    def convert_layout(self, arg):

        # remove "#define LAYOUTxxx(" from arg

        str2 = re.sub(r'(\(|{|})', '', arg)

        str2 = str2.replace('#define', '')

        str2 = str2.replace(self.search_layout, '')

        str2 = str2.replace(' ', '')


        # split layout macro to "LAYOUT macro" arguments and array definition

        # place = "R00, R01\nL01, L00"

        # array = "R00, R01, L00, L01"

        [place, array] = str2.split(')')


        # remove spaces

        array = array.replace('\n', '')

        array = array.replace('\r', '')

        array = array.replace('\t', '')

        array = array.replace('\\', '')

        place = place.replace('\r', '')

        place = place.replace('\t', '')

        place = place.replace('\\', '')


        array = array.split(',')


        # pick up a element from LAYOUT macro arguments and find it from array

        layout = []

        for row in place.split('\n'):

            if row == '':

                continue

            for key in row.split(','):

                if key == '':

                    continue

                idx = array.index(key)

                layout.append(idx + 1)

            layout.append(0)


        layout_str = str(layout)

        layout_str = layout_str.replace(' 0,', ' 0,\n')

        layout_str = layout_str.replace(', 0]', ']')


        return layout_str


    def waring_multiple_definition(self, symbol, f, line):

        print(yellow('warning') +

              f': multiple definition of {symbol} in {f.name}:{line}. Previous definition is used.')


    def remove_comment(self, arg):

        return arg.split('//')[0]


    def parse_file(self, f):

        cform = f.readlines()

        layout_detect = False

        layout_str = ''

        line = 0

        for string in cform:

            line = line + 1

            string = self.remove_comment(string)

            if 'VENDOR_ID' in string:

                self.vid = string.split('VENDOR_ID')[

                    1].replace('\n', '').strip()

            elif 'PRODUCT_ID' in string:

                self.pid = string.split('PRODUCT_ID')[

                    1].replace('\n', '').strip()

            elif 'MANUFACTURER' in string:

                self.manufacture = string.split('MANUFACTURER')[

                    1].replace('\n', '').strip()

            elif 'PRODUCT' in string:

                self.name = string.split('PRODUCT')[

                    1].replace('\n', '').strip()

            elif 'DESCRIPTION' in string:

                self.description = string.split('DESCRIPTION')[

                    1].replace('\n', '').strip()

            elif re.match(r'\s*#\s*define\s+MATRIX_ROWS\s', string):

                if self.row_num != '':

                    self.waring_multiple_definition('MATRIX_ROWS', f, line)

                else:

                    self.row_num = string.split('MATRIX_ROWS')[

                        1].replace('\n', '').strip()

            elif re.match(r'\s*#\s*define\s+MATRIX_COLS\s', string):

                if self.col_num != '':

                    self.waring_multiple_definition('MATRIX_COLS', f, line)

                else:

                    self.col_num = string.split('MATRIX_COLS')[

                        1].replace('\n', '').strip()

            elif re.match(r'\s*#\s*define\s+MATRIX_ROW_PINS\s', string):

                if self.row_pins != '':

                    self.waring_multiple_definition('MATRIX_ROW_PINS', f, line)

                else:

                    self.row_pins = self.convert_pindef(string)

            elif re.match(r'\s*#\s*define\s+MATRIX_COL_PINS\s', string):

                if self.col_pins != '':

                    self.waring_multiple_definition('MATRIX_COL_PINS', f, line)

                else:

                    self.col_pins = self.convert_pindef(string)

            elif re.match(r'\s*#\s*define\s+MATRIX_ROW_PINS_RIGHT\s', string):

                if self.row_pins_right != '':

                    self.waring_multiple_definition(

                        'MATRIX_ROW_PINS_RIGHT', f, line)

                else:

                    self.row_pins_right = self.convert_pindef(string)

            elif re.match(r'\s*#\s*define\s+MATRIX_COL_PINS_RIGHT\s', string):

                if self.col_pins_right != '':

                    self.waring_multiple_definition(

                        'MATRIX_COL_PINS_RIGHT', f, line)

                else:

                    self.col_pins_right = self.convert_pindef(string)

            elif re.match(r'\s*#\s*define\s+DIODE_DIRECTION\s', string):

                if self.diode_direction != '':

                    self.waring_multiple_definition('DIODE_DIRECTION', f, line)

                else:

                    direction = string.split('DIODE_DIRECTION')[

                        1].replace('\n', '').strip()

                    if direction == 'ROW2COL':

                        self.diode_direction = 1

                    else:

                        self.diode_direction = 0

            elif re.match(r'\s*#\s*define\s+RGB_DI_PIN\s', string):

                if self.led_pin != '':

                    self.waring_multiple_definition('RGB_DI_PIN', f, line)

                else:

                    pin = string.split('RGB_DI_PIN')[

                        1].replace('\n', '').strip()

                    self.led_pin = self.PIN_TABLE[pin]

            elif re.match(r'\s*#\s*define\s+RGBLED_NUM\s', string):

                if self.led_num != '':

                    self.waring_multiple_definition('RGBLED_NUM', f, line)

                else:

                    self.led_num = string.split('RGBLED_NUM')[

                        1].replace('\n', '').strip()

            elif '#define ' + self.search_layout + '(' in string:

                if self.layout != '':

                    self.waring_multiple_definition(

                        self.search_layout, f, line)

                else:

                    layout_detect = True


            if layout_detect:

                layout_str = layout_str + string


            if '}\n' in string and layout_detect:

                layout_detect = False

                self.layout = self.convert_layout(layout_str)


        return


    def assertion(self):

        res = True

        if self.layout == '':

            print(red('ERROR') + ': Failed to find LAYOUT')

            res = False


        if self.col_pins == '':

            print(red('ERROR') + ': Failed to find MATRIX_COL_PINS')

            res = False


        if self.row_pins == '':

            print(red('ERROR') + ': Failed to find MATRIX_ROW_PINS')

            res = False


        if self.col_num == '':

            print(red('ERROR') + ': Failed to find MATRIX_COLS')

            res = False


        if self.row_num == '':

            print(red('ERROR') + ': Failed to find MATRIX_ROWS')

            res = False


        if (int(self.col_num) != len(self.col_pins.split(',')) or int(self.row_num) != len(self.row_pins.split(','))):

            self.is_split = True

            print('This keyboard is split keyboard')


        if (int(self.col_num) != len(self.col_pins.split(',')) and int(self.row_num) != len(self.row_pins.split(','))):

            print(yellow('warning') +

                  ': MATRIX len and PINS len do not match in both row and col.')


        return res


    def apply_default(self):

        if self.diode_direction == '':

            print('DIODE_DIRECTION is not found. Apply ROW2COL.')

            self.diode_direction = '0'


        if self.led_pin == '':

            print('RGB_DI_PIN is not found. Apply 255(not used)')

            self.led_pin = '255'


        if self.led_num == '':

            print('RGBLED_NUM is not found. Apply 0(not used)')

            self.led_num = '0'


        if self.is_split:

            if self.col_pins_right == '':

                self.col_pins_right = self.col_pins


            if self.row_pins_right == '':

                self.row_pins_right = self.row_pins



if __name__ == '__main__':

    args = sys.argv

    if len(args) == 1:

        print(

            ('Usage : config_converter.py [TARGET]\n' ' ex: ./config_covnerte.py ~/qmk_firmware/keyboards/helix/rev2\n'))

        sys.exit()


    config = ConfigConverter()


    dirc = args[1]

    files = os.listdir(dirc)


    print(f' ** Making config.json from {dirc} ** ')


    for fname in files:

        path = os.path.join(dirc, fname)

        if not '.h' in path:

            continue

        with open(path) as f:

            config.find_layout_macro(f)


    config.print_layout_list()


    if len(config.layout_macro) == 1:

        layout_idx = 0

    else:

        print('')

        layout_idx = input('Use layout: ')


    try:

        config.set_layout_index(layout_idx)

    except:

        print('invalid input')

        sys.exit()


    for fname in files:

        path = os.path.join(dirc, fname)

        if not '.h' in path:

            continue

        with open(path) as f:

            config.parse_file(f)


    if not config.assertion():

        sys.exit()


    config.apply_default()


    config_name = dirc.split('keyboards/')[1]

    if '/' in config_name:

        config_name = config_name.replace('/', '_')


    if config_name[-1] != '_':

        config_name = config_name + '_'


    if config.search_layout != 'LAYOUT':

        config_name = config_name + config.search_layout.lower() + '_'


    mode = 'SINGLE'

    if config.is_split:

        config_name = config_name + 'master_left_'

        mode = 'SPLIT_MASTER'


    config_name = config_name + 'config.json'


    config_file_format = (

        '{{"config":\n\t{{\n\t\t"version":2,\n\t\t"device_info":{{"vid":"{0}","pid":"{1}",\n\t\t\t"name":"{2}","manufacture":"{3}","description":"{4}"}},\n'

        '\t\t"matrix":{{"rows":{5},"cols":{6},"device_rows":{7}, "device_cols":{8},\n\t\t\t"debounce":1,"is_left_hand":1,"diode_direction":{9},\n\t\t\t"row_pins":{10},\n\t\t\t"col_pins":{11},\n'

        '\t\t"layout":{12}}},\n\t\t"mode":"{13}","startup":1,\n\t\t"peripheral":{{"max_interval":60,"min_interval":30,"slave_latency":7}},\n'

        '\t\t"central":{{"max_interval":60,"min_interval":30,"slave_latency":0}},\n'

        '\t\t"led":{{"pin":{14}, "num":{15}}},\n\t\t"keymap":{{"locale":"US","use_ascii":0}}\n}}}}'

    )


    with open(config_name, 'w') as f:

        f.write(

            config_file_format.format(

                config.vid, config.pid, config.name, config.manufacture, config.description, config.row_num, config.col_num, len(

                    config.row_pins.split(',')), len(config.col_pins.split(',')), config.diode_direction, config.row_pins, config.col_pins,

                config.layout.replace(

                    '\n', '\n\t\t\t'), mode, config.led_pin, config.led_num

            )

        )


    print(f' ** Output to {config_name} ** \n')


    if not config.is_split:

        sys.exit()


    config_name = config_name.replace('master_left_', 'slave_right_')

    mode = 'SPLIT_SLAVE'

    with open(config_name, 'w') as f:

        f.write(

            config_file_format.replace('"is_left_hand":1', '"is_left_hand":0').format(

                config.vid, config.pid, config.name, config.manufacture, config.description, config.row_num, config.col_num, len(

                    config.row_pins_right.split(',')), len(config.col_pins_right.split(',')), config.diode_direction, config.row_pins_right,

                config.col_pins_right, config.layout.replace(

                    '\n', '\n\t\t\t'), mode, config.led_pin, config.led_num

            )

        )


    print(f' ** Output to {config_name} ** \n')


    config_name = config_name.replace('slave_right_', 'lpme_left_')

    lpme_row = ([int(r) for r in config.row_pins.strip('[]').split(',')])

    lpme_row.extend([int(r)

                     for r in config.row_pins_right.strip('[]').split(',')])

    lpme_col = ([int(r) for r in config.col_pins.strip('[]').split(',')])

    lpme_col.extend([int(r)

                     for r in config.col_pins_right.strip('[]').split(',')])

    mode = 'SINGLE'

    with open(config_name, 'w') as f:

        f.write(

            config_file_format.format(

                config.vid, config.pid, config.name, config.manufacture,

                config.description, config.row_num, config.col_num,

                len(config.row_pins.split(',')), len(

                    config.col_pins.split(',')),

                str(int(config.diode_direction) + 2),

                str(lpme_row),

                str(lpme_col),

                config.layout.replace('\n', '\n\t\t\t'),

                mode,

                config.led_pin, config.led_num

            )

        )


    print(f' ** Output to {config_name} ** \n')



このスクリプトにQMK Firmware内のmeishi2のフォルダを食わせればいいかんじのconfig.jsonを出力した。なるほど!?